package com.shengqin.controller.api;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.shengqin.common.pojo.CommonResult;
import com.shengqin.config.CommonException;
import com.shengqin.controller.api.dto.*;
import com.shengqin.entity.*;
import com.shengqin.pojo.plan.result.ScoreResult;
import com.shengqin.pojo.plan.result.TestResultKey;
import com.shengqin.service.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.test.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.annotation.PreDestroy;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/v2/testResult")
@Slf4j
public class UploadTestResultController {

    // 创建线程池，核心线程数设置为处理器数量的2倍
    private final ExecutorService executorService = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors() * 2
    );

    // 在应用关闭时关闭线程池
    @PreDestroy
    public void shutdown() {
        if (!executorService.isShutdown()) {
            executorService.shutdown();
        }
    }

    @Resource
    private IPlanService planService;
    @Resource
    private IPlanStudentService planStudentService;
    @Resource
    private ITestResultService testResultService;
    @Resource
    private IResultRecordService resultRecordService;
    @Resource
    private IPlanApplyProjectService applyProjectService;
    @Resource
    private SysHealthProjectService healthProjectService;
    @Resource
    private IStandardService standardService;
    @Resource
    private ITestResultProcessService testResultProcessService;
    @Resource
    private ITestResultHandlerService testResultHandlerService;

    // Constants
    private static final String BMI_PROJECT = "BMI";
    private static final String HEIGHT_PROJECT = "身高";
    private static final String WEIGHT_PROJECT = "体重";
    private static final String TIMING_PROJECT = "计时";
    private static final String RULE_TYPE_ONE = "1";
    private static final String RULE_TYPE_TWO = "2";
    private static final String RESULT_SEPARATOR = "/";
    private static final String TIME_SEPARATOR = ":";
    private static final String MINUTE_SYMBOL = "′";
    private static final String SECOND_SYMBOL = "″";

    @PostMapping("/upload")
    public CommonResult uploadResultV2(@RequestBody TestResultUploadParam resultUploadParam) {
        Plan plan = validatePlan(resultUploadParam.getPlanId());
        validateUploadData(resultUploadParam);

        Integer templateId = plan.getTemplateId();
        List<Standard> templateStandardList = standardService.listByTemplateId(templateId);
        if (CollectionUtils.isEmpty(templateStandardList)) {
            throw new CommonException("评分模板中没有设置评分标准数据，请检查！");
        }
        List<SysHealthProject> templateProjects = healthProjectService.listByTemplateId(templateId);
        if (CollectionUtils.isEmpty(templateProjects)) {
            throw new CommonException("评分模板中没有设置项目数据，请检查！");
        }
        Map<TestResultKey, List<Standard>> standardMap = templateStandardList.stream().collect(Collectors.groupingBy(t -> new TestResultKey(t.getProjectName(), t.getSex(), t.getGradeId())));
        Map<String, SysHealthProject> projectMap = templateProjects.stream().collect(Collectors.toMap(t -> t.getProjectName() + "_" + t.getGradeId(), p -> p));

        List<TestResult> testResultList = buildTestResults(resultUploadParam, plan);
        processTestResults(testResultList, plan, standardMap, projectMap);

        return CommonResult.data(true).setMsg("上传成绩成功");
    }

    // Validation Methods
    private void validateUploadData(TestResultUploadParam resultUploadParam) {
        if (CollectionUtils.isEmpty(resultUploadParam.getScoreList())) {
            throw new CommonException("无数据需要上传");
        }
    }

    private Plan validatePlan(String planId) {
        Plan plan = planService.getByCode(planId);
        if (plan == null) {
            throw new CommonException("未查询到该计划编码，请检查");
        }
        if (plan.getState().equals(3)) {
            throw new CommonException("计划已结束，暂时无法上传成绩");
        }
        return plan;
    }


    // Result Update Methods
    private void processTestResults(List<TestResult> testResultList, Plan plan, Map<TestResultKey, List<Standard>> standardMap, Map<String, SysHealthProject> projectMap) {
        // 按学籍号分组
        Map<String, List<TestResult>> groupedResults = testResultList.stream()
                .collect(Collectors.groupingBy(TestResult::getStudyCode));

        // 对每个学籍号的成绩列表进行异步处理，使用线程池
        groupedResults.forEach((studyCode, results) -> {
            CompletableFuture.runAsync(() -> {
                try {
                    // 先找出身高体重项目
                    List<TestResult> heightWeightResults = results.stream()
                            .filter(result -> HEIGHT_PROJECT.equals(result.getPrjName()) || 
                                            WEIGHT_PROJECT.equals(result.getPrjName()))
                            .collect(Collectors.toList());

                    // 处理其他项目
                    for (TestResult testResult : results) {
                        // 跳过身高体重项目，它们会随 BMI 一起处理
                        if (HEIGHT_PROJECT.equals(testResult.getPrjName()) || 
                            WEIGHT_PROJECT.equals(testResult.getPrjName())) {
                            continue;
                        }

                        SysHealthProject healthProject = projectMap.getOrDefault(testResult.getPrjName() + "_" + testResult.getGradeId(), null);
                        if (healthProject == null) {
                            log.error("评分模板里未找到对应的测试项目: {}", JSON.toJSONString(testResult));
                            continue;
                        }

                        List<Standard> prjStandardList = standardMap.get(new TestResultKey(testResult.getPrjName(), testResult.getSex(), testResult.getGradeId().toString()));
                        if (CollectionUtils.isEmpty(prjStandardList)) {
                            log.error("评分模板里未找到对应的评分标准: {}", JSON.toJSONString(testResult));
                            continue;
                        }

                        // 计算得分
                        ScoreResult scoreResult = standardService.buildScoreResult(testResult.getResult(), prjStandardList, healthProject, plan.getCalculateType(), healthProject.getRadixLength());
                        if (scoreResult != null) {
                            updateTestResultScore(testResult, scoreResult, healthProject.getPrjWeight());
                        }

                        // 处理不同类型的项目
                        if (BMI_PROJECT.equalsIgnoreCase(testResult.getPrjName())) {
                            testResultHandlerService.handleBmiResult(testResult, heightWeightResults, plan.getId());
                        } else {
                            testResultProcessService.processTestResult(testResult, plan, new ITestResultProcessService.TestResultProcessor() {
                                @Override
                                public void processExistingResult(TestResult testResult, TestResult existingResult, Plan plan) {
                                    if (RULE_TYPE_ONE.equals(plan.getPlanRuleType())) {
                                        testResultHandlerService.handleRuleTypeOne(testResult, existingResult, plan.getId());
                                    } else if (RULE_TYPE_TWO.equals(plan.getPlanRuleType())) {
                                        testResultHandlerService.handleRuleTypeTwo(testResult, existingResult, plan);
                                    }
                                }

                                @Override
                                public void saveNewResult(TestResult testResult, Integer planId) {
                                    testResultHandlerService.saveTestResult(testResult, planId);
                                }
                            });
                        }
                    }
                } catch (Exception e) {
                    log.error("处理学籍号 {} 的��试结果出错", studyCode, e);
                    throw new RuntimeException(e);
                }
            }, executorService);
        });
    }

    private void updateTestResultScore(TestResult testResult, ScoreResult scoreResult, BigDecimal prjWeight) {
        if (scoreResult.getScore() != null) {
            testResult.setScore(new BigDecimal(scoreResult.getScore().toString()));
            testResult.setStandardScore(scoreResult.getStandardScore());
            testResult.setExtraScore(scoreResult.getExtraScore());
            testResult.setLevel(scoreResult.getLevel());
            if (prjWeight != null) {
                if (scoreResult.getExtraScore().compareTo(BigDecimal.ZERO) > 0) {
                    BigDecimal finalScore = scoreResult.getScore().subtract(scoreResult.getExtraScore()).setScale(4, RoundingMode.HALF_UP);
                    testResult.setFinalScore(finalScore.multiply(prjWeight).add(scoreResult.getExtraScore()));
                } else {
                    testResult.setFinalScore(scoreResult.getScore().multiply(prjWeight).setScale(4, RoundingMode.HALF_UP).add(scoreResult.getExtraScore()));
                }
            }
        } else {
            testResult.setScore(BigDecimal.ZERO);
            testResult.setStandardScore(BigDecimal.ZERO);
            testResult.setExtraScore(BigDecimal.ZERO);
            testResult.setFinalScore(BigDecimal.ZERO);
            testResult.setLevel("不及格");
        }
    }

    // Helper Methods
    private List<ResultRecord> buildResultRecords(TestResultUploadParam testResultUploadParam, Integer planId) {
        return testResultUploadParam.getScoreList().stream().map(scoreItem -> {
            ResultRecord resultRecord = new ResultRecord();
            BeanUtils.copyProperties(scoreItem, resultRecord);
            resultRecord.setPlanId(planId);
            resultRecord.setUploadType(testResultUploadParam.getUploadType());
            resultRecord.setCreateTime(LocalDateTime.now());
            return resultRecord;
        }).collect(Collectors.toList());
    }

    private Set<String> extractStudyCodes(List<UploadScoreItem> scoreItems) {
        return scoreItems.stream()
                .map(UploadScoreItem::getStudyCode)
                .collect(Collectors.toSet());
    }

    private void filterScoreItems(TestResultUploadParam resultUploadParam, Plan plan, List<PlanApplyProject> applyProjectList) {
        Map<String, List<PlanApplyProject>> studyApplyProjectMap = applyProjectList.stream()
                .collect(Collectors.groupingBy(PlanApplyProject::getStudyCode));
        if (plan.getExamFlag()) {
            resultUploadParam.getScoreList().removeIf(item -> {
                List<PlanApplyProject> planApplyProjects = studyApplyProjectMap.getOrDefault(item.getStudyCode(), null);
                if (CollectionUtils.isEmpty(planApplyProjects)) {
                    return true;
                }
                PlanApplyProject planApplyProject = planApplyProjects.get(0);
                if (!StringUtils.hasLength(planApplyProject.getProjectNames())) {
                    return true;
                }
                return !planApplyProject.getProjectNames().contains(item.getPrjName());
            });
        }

        if (resultUploadParam.getForce() != null && !resultUploadParam.getForce()) {
            if (plan.getExamFlag() && !CollectionUtils.isEmpty(applyProjectList)) {
                List<PlanApplyProject> lockedList = applyProjectList.stream()
                        .filter(item -> item.getLocked() != null && item.getLocked())
                        .collect(Collectors.toList());
                Set<String> lockedStudyCodes = lockedList.stream()
                        .map(PlanApplyProject::getStudyCode)
                        .collect(Collectors.toSet());
                resultUploadParam.getScoreList().removeIf(scoreItem -> lockedStudyCodes.contains(scoreItem.getStudyCode()));
            }
        }
    }

    private BigDecimal transferMillisecond(String millisecond, String radixType, Integer radixLength) {
        if (StringUtils.hasLength(millisecond)) {
            if ("60".equals(radixType)) {
                // 60进制
                return new BigDecimal(millisecond).divide(new BigDecimal("60"), radixLength, RoundingMode.HALF_UP);
            } else if ("100".equals(radixType)) {
                // 100进制
                return new BigDecimal(millisecond).divide(new BigDecimal("100"), radixLength, RoundingMode.HALF_UP);
            } else {
                // 1000进制
                return new BigDecimal(millisecond).divide(new BigDecimal("1000"), radixLength, RoundingMode.HALF_UP);
            }
        }
        return BigDecimal.ZERO;
    }

    private void handleRuleTypeTwo(TestResult testResult, TestResult existingResult, Plan plan) {
        int scoreComparison = testResult.getFinalScore().compareTo(existingResult.getFinalScore());
        if (scoreComparison > 0) {
            updateTestResult(testResult, existingResult, plan.getId());
        } else if (scoreComparison == 0) {
            compareAndUpdateResult(testResult, existingResult, plan);
        }
    }

    private void compareAndUpdateResult(TestResult testResult, TestResult existingResult, Plan plan) {
        SysHealthProject healthProject = healthProjectService.listByPrjCodeAndGradeId(
                testResult.getPrjName(),
                String.valueOf(testResult.getGradeId()),
                plan.getTemplateId()
        );

        if (healthProject == null) {
            return;
        }

        String projectType = healthProject.getPrjType();
        if (isTimingProject(projectType)) {
            compareTimingResult(testResult, existingResult, plan, healthProject);
        } else {
            compareNormalResult(testResult, existingResult, plan.getId());
        }
    }

    private void compareTimingResult(TestResult testResult, TestResult existingResult,
                                     Plan plan, SysHealthProject healthProject) {
        try {
            BigDecimal newResult = transferResult(testResult.getResult(),
                    plan.getCalculateType() == 2 ? healthProject.getRadixType() : "",
                    healthProject.getRadixLength());
            BigDecimal oldResult = transferResult(existingResult.getResult(),
                    plan.getCalculateType() == 2 ? healthProject.getRadixType() : "",
                    healthProject.getRadixLength());

            if (newResult != null && oldResult != null && newResult.compareTo(oldResult) < 0) {
                updateTestResult(testResult, existingResult, plan.getId());
            }
        } catch (Exception e) {
            log.error("比较计时项目成绩出错, testResult={}, existingResult={}",
                    JSON.toJSONString(testResult), JSON.toJSONString(existingResult), e);
        }
    }

    private void compareNormalResult(TestResult testResult, TestResult existingResult, Integer planId) {
        try {
            BigDecimal newResult = new BigDecimal(testResult.getResult());
            BigDecimal oldResult = new BigDecimal(existingResult.getResult());
            if (newResult.compareTo(oldResult) > 0) {
                updateTestResult(testResult, existingResult, planId);
            }
        } catch (Exception e) {
            log.error("比较非计时项目成绩出错, testResult={}, existingResult={}",
                    JSON.toJSONString(testResult), JSON.toJSONString(existingResult), e);
        }
    }

    private BigDecimal transferResult(String result, String radixType, Integer radixLength) {
        if (StringUtils.hasLength(result)) {
            if (result.contains(TIME_SEPARATOR)) {
                result = result.replaceAll("\\.", TIME_SEPARATOR);
                String[] texts = result.split(TIME_SEPARATOR);
                BigDecimal minutes = new BigDecimal(Integer.parseInt(texts[0]) * 60);
                BigDecimal second = new BigDecimal(Integer.parseInt(texts[1]));
                BigDecimal millisecond = new BigDecimal(Integer.parseInt(texts[2]))
                        .divide(new BigDecimal("1000"), 3, RoundingMode.DOWN);

                if (StringUtils.hasLength(radixType)) {
                    BigDecimal millisecondDecimal = transferMillisecond(texts[2], radixType, radixLength);
                    return minutes.add(second).add(millisecondDecimal);
                }
                return minutes.add(second).add(millisecond);
            } else if (result.contains(MINUTE_SYMBOL) || result.contains("'")) {
                String finalValue = result.replaceAll("'", MINUTE_SYMBOL)
                        .replaceAll("\"", SECOND_SYMBOL);

                String[] values = finalValue.split(MINUTE_SYMBOL);
                Integer valueMin = Integer.parseInt(values[0]) * 60;
                Integer valueSecond = Integer.valueOf(values[1].replaceAll(SECOND_SYMBOL, ""));
                return new BigDecimal(valueMin + valueSecond);
            } else {
                return new BigDecimal(result);
            }
        }
        return null;
    }

    private void updateTestResult(TestResult testResult, TestResult existingResult, Integer planId) {
        testResult.setId(existingResult.getId());
        testResultService.updateById(testResult);

        if (BMI_PROJECT.equalsIgnoreCase(testResult.getPrjName())) {
            updateHeightWeightRecords(testResult, planId);
        }
    }

    private boolean isTimingProject(String projectType) {
        return StringUtils.hasLength(projectType) &&
                (projectType.contains(TIMING_PROJECT) || projectType.equalsIgnoreCase(TIMING_PROJECT));
    }

    private void updateHeightWeightRecords(TestResult bmiResult, Integer planId) {
        HeightWeightData heightWeightData = parseHeightWeightData(bmiResult.getResult());
        if (heightWeightData == null) {
            return;
        }

        List<TestResult> heightWeightResults = findHeightWeightResults(bmiResult.getStudyCode(), planId);
        if (!CollectionUtils.isEmpty(heightWeightResults)) {
            updateExistingHeightWeightRecords(heightWeightResults, heightWeightData, bmiResult);
        } else {
            createNewHeightWeightRecords(heightWeightData, bmiResult, planId);
        }
    }

    private HeightWeightData parseHeightWeightData(String bmiResult) {
        try {
            String[] heightWeight = bmiResult.split(RESULT_SEPARATOR);
            if (heightWeight.length >= 2) {
                return new HeightWeightData(heightWeight[0], heightWeight[1]);
            }
            log.error("BMI结果格式错误: {}", bmiResult);
        } catch (Exception e) {
            log.error("解析BMI结果出错: {}", bmiResult, e);
        }
        return null;
    }

    private List<TestResult> findHeightWeightResults(String studyCode, Integer planId) {
        QueryWrapper<TestResult> heightWeightQuery = new QueryWrapper<>();
        heightWeightQuery.lambda()
                .eq(TestResult::getPlanId, planId)
                .eq(TestResult::getStudyCode, studyCode)
                .in(TestResult::getPrjName, Arrays.asList(HEIGHT_PROJECT, WEIGHT_PROJECT));
        return testResultService.list(heightWeightQuery);
    }

    private void updateExistingHeightWeightRecords(List<TestResult> heightWeightResults,
                                                   HeightWeightData heightWeightData,
                                                   TestResult bmiResult) {
        for (TestResult result : heightWeightResults) {
            if (HEIGHT_PROJECT.equals(result.getPrjName())) {
                updateHeightWeightResult(result, heightWeightData.getHeight(), bmiResult);
            } else if (WEIGHT_PROJECT.equals(result.getPrjName())) {
                updateHeightWeightResult(result, heightWeightData.getWeight(), bmiResult);
            }
            testResultService.updateById(result);
        }
    }

    private void updateHeightWeightResult(TestResult result, String measureValue, TestResult bmiResult) {
        result.setResult(measureValue);
        result.setScore(bmiResult.getScore());
        result.setStandardScore(bmiResult.getStandardScore());
        result.setExtraScore(bmiResult.getExtraScore());
        result.setFinalScore(bmiResult.getFinalScore());
        result.setLevel(bmiResult.getLevel());
    }

    private void createNewHeightWeightRecords(HeightWeightData heightWeightData,
                                              TestResult bmiResult,
                                              Integer planId) {
        createHeightWeightRecord(HEIGHT_PROJECT, heightWeightData.getHeight(), bmiResult, planId);
        createHeightWeightRecord(WEIGHT_PROJECT, heightWeightData.getWeight(), bmiResult, planId);
    }

    private void createHeightWeightRecord(String projectName, String result,
                                          TestResult bmiResult, Integer planId) {
        TestResult newResult = new TestResult();
        BeanUtils.copyProperties(bmiResult, newResult, "id", "prjName", "result");
        newResult.setPrjName(projectName);
        newResult.setResult(result);
        newResult.setCreateTime(LocalDateTime.now());
        testResultService.save(newResult);
    }

    // Result Processing Methods
    private List<TestResult> buildTestResults(TestResultUploadParam resultUploadParam, Plan plan) {
        List<ResultRecord> resultRecords = processResultRecords(resultUploadParam, plan);
        Set<String> studyCodes = validateAndExtractStudyCodes(resultUploadParam);
        processApplyProjects(resultUploadParam, plan, studyCodes);
        Map<String, PlanStudent> studentMap = getStudentMap(plan.getId(), studyCodes);
        return testResultService.buildTestResultList(resultRecords, studentMap);
    }

    private List<ResultRecord> processResultRecords(TestResultUploadParam resultUploadParam, Plan plan) {
        List<ResultRecord> resultRecords = buildResultRecords(resultUploadParam,plan.getId());
        if (!CollectionUtils.isEmpty(resultRecords)) {
            resultRecordService.saveBatch(resultRecords);
        }
        return resultRecords;
    }

    private Set<String> validateAndExtractStudyCodes(TestResultUploadParam resultUploadParam) {
        Set<String> studyCodes = extractStudyCodes(resultUploadParam.getScoreList());
        if (CollectionUtils.isEmpty(studyCodes)) {
            throw new CommonException("上传成绩数据中缺少学籍号数据");
        }
        return studyCodes;
    }

    private void processApplyProjects(TestResultUploadParam resultUploadParam, Plan plan, Set<String> studyCodes) {
        List<PlanApplyProject> applyProjectList = applyProjectService.queryByPlanIdAndStudyCodes(plan.getId(), studyCodes);
        filterScoreItems(resultUploadParam, plan, applyProjectList);
    }

    private Map<String, PlanStudent> getStudentMap(Integer planId, Set<String> studyCodes) {
        List<PlanStudent> students = planStudentService.queryByPlanIdAndStudyCodes(planId, studyCodes);
        if (CollectionUtils.isEmpty(students)) {
            throw new CommonException("计划未导入考生数据，请检查！");
        }
        return students.stream().collect(Collectors.toMap(PlanStudent::getStudyCode, s -> s));
    }

    @Data
    private static class HeightWeightData {
        private final String height;
        private final String weight;

        public HeightWeightData(String height, String weight) {
            this.height = height;
            this.weight = weight;
        }
    }
} 